package cmd

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"metrics-collect/pkg/metrics"
	"metrics-collect/pkg/plugins"
	"metrics-collect/pkg/plugins/cpu"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"go.uber.org/zap"
)

var (
	cfgFile string
	logger  *zap.Logger
)

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "metrics-collect",
	Short: "A metrics collection application with plugin support",
	Long: `A flexible metrics collection application that supports various plugins
and exports metrics in Prometheus format. It can collect system metrics
like CPU, memory, and more through its plugin system.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		// Initialize logger
		var err error
		logger, err = initLogger()
		if err != nil {
			return fmt.Errorf("failed to initialize logger: %v", err)
		}
		defer logger.Sync()

		// Start the metrics collection
		return startMetricsCollection()
	},
}

// Execute adds all child commands to the root command and sets flags appropriately.
func Execute() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func init() {
	cobra.OnInitialize(initConfig)
	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./config.yaml)")
}

// initConfig reads in config file and ENV variables if set
func initConfig() {
	if cfgFile != "" {
		viper.SetConfigFile(cfgFile)
	} else {
		viper.AddConfigPath(".")
		viper.SetConfigName("config")
	}

	viper.AutomaticEnv()

	if err := viper.ReadInConfig(); err != nil {
		fmt.Printf("Error reading config file: %s\n", err)
		os.Exit(1)
	}
}

func initLogger() (*zap.Logger, error) {
	logLevel := viper.GetString("global.log_level")
	config := zap.NewProductionConfig()

	switch logLevel {
	case "debug":
		config.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
	case "info":
		config.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
	case "warn":
		config.Level = zap.NewAtomicLevelAt(zap.WarnLevel)
	case "error":
		config.Level = zap.NewAtomicLevelAt(zap.ErrorLevel)
	default:
		config.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
	}

	return config.Build()
}

func startMetricsCollection() error {
	// Create metrics server
	prometheusPort := viper.GetInt("prometheus.port")
	prometheusPath := viper.GetString("prometheus.path")
	if prometheusPath == "" {
		prometheusPath = "/metrics"
	}

	server := metrics.NewServer(prometheusPort, prometheusPath, logger)
	collector := metrics.NewCollectorManager(server, logger)

	// Register and initialize plugins
	if err := registerPlugins(collector); err != nil {
		return fmt.Errorf("failed to register plugins: %v", err)
	}

	// Start metrics server in a goroutine
	go func() {
		if err := server.Start(); err != nil {
			logger.Error("Metrics server error", zap.Error(err))
			os.Exit(1)
		}
	}()

	// Handle graceful shutdown
	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
	<-stop

	logger.Info("Shutting down...")
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	return server.Stop(ctx)
}

func registerPlugins(collector *metrics.CollectorManager) error {
	globalInterval := viper.GetDuration("global.collect_interval")
	pluginConfigs := viper.Get("plugins").([]interface{})

	for _, p := range pluginConfigs {
		pluginConfig := p.(map[string]interface{})
		name := pluginConfig["name"].(string)
		enabled := pluginConfig["enabled"].(bool)

		if !enabled {
			continue
		}

		interval := globalInterval
		if pluginInterval, ok := pluginConfig["interval"].(string); ok {
			if d, err := time.ParseDuration(pluginInterval); err == nil {
				interval = d
			}
		}

		settings := pluginConfig["settings"].(map[string]interface{})

		var instance plugins.Plugin
		switch name {
		case "cpu":
			instance = cpu.NewCPUPlugin()
		// Add other plugins here
		default:
			logger.Warn("Unknown plugin", zap.String("name", name))
			continue
		}

		if err := collector.RegisterPlugin(instance); err != nil {
			return fmt.Errorf("failed to register plugin %s: %v", name, err)
		}

		if err := collector.InitializePlugin(name, settings, interval); err != nil {
			return fmt.Errorf("failed to initialize plugin %s: %v", name, err)
		}

		if err := collector.StartPlugin(name); err != nil {
			return fmt.Errorf("failed to start plugin %s: %v", name, err)
		}
	}

	return nil
}
